<script setup lang="ts">
import axios from 'axios'
import SparkMD5 from 'spark-md5'
import { ref } from 'vue'

const file = ref<File | null>(null)
const CHUNK_SIZE = 1024 * 1024
// 选择文件
function handleFileChange(e: Event) {
  const target = e.target as HTMLInputElement
  if (target.files && target.files.length > 0) {
    file.value = target.files[0]
  }
  else {
    file.value = null
  }
}
// 文件切片
function createChunks(file: File) {
  let cur = 0
  const chunks = []

  while (cur < file.size) {
    const chunk = file.slice(cur, cur + CHUNK_SIZE)
    chunks.push(chunk)
    cur += CHUNK_SIZE
  }

  console.log(chunks)
  return chunks
}
// 计算 md5
function calculateMD5(chunks: Blob[]) {
  return new Promise((resolve) => {
    // 1.第一个和最后一个切片全部参与计算
    // 2.中间的切片只计算前两个字节、中间两个字节、最后两个字节
    const target: Blob[] = [] // 存储需要计算md5的切片
    const spark = new SparkMD5.ArrayBuffer()
    const fileReader = new FileReader()

    chunks.forEach((chunk, index) => {
      if (index === 0 || index === chunks.length - 1) {
        target.push(chunk)
      }
      else {
        target.push(chunk.slice(0, 2))
        target.push(chunk.slice(CHUNK_SIZE / 2, CHUNK_SIZE / 2 + 2))
        target.push(chunk.slice(chunk.size - 2, chunk.size))
      }
    })

    fileReader.readAsArrayBuffer(new Blob(target))
    fileReader.onload = (e) => {
      if (!e.target || !(e.target.result instanceof ArrayBuffer))
        return
      spark.append(e.target.result as ArrayBuffer)

      return resolve(spark.end())
    }
  })
}
async function uploadFile() {
  if (!file.value)
    return
  // 开始分片
  const chunks = createChunks(file.value)
  // hash值
  const hash = await calculateMD5(chunks)

  const totalChunks = Math.ceil(file.value.size / CHUNK_SIZE) // 计算总块数

  const { data } = await axios.get('http://127.0.0.1:3007/exists', {
    params: { fileHash: hash, fileName: file.value.name },
  })

  if (data.exists) {
    console.log('文件已存在')
    return
  }

  const max = 6 // 最大并发数量
  const taskPool: Promise<void>[] = [] // 任务池

  for (let i = 0; i < chunks.length; i++) {
    const formData = new FormData()
    formData.append('fileChunk', chunks[i])
    formData.append('index', i.toString())
    formData.append('fileHash', hash as string)

    const task = axios
      .post('http://127.0.0.1:3007/upload', formData)
      .then(() => {
        // 使用 splice 从任务池中移除任务
        const index = taskPool.indexOf(task)
        if (index !== -1)
          taskPool.splice(index, 1)
      })

    taskPool.push(task)

    // 控制最大并发数量
    if (taskPool.length === max) {
      // 等待任意一个任务完成
      await Promise.race(taskPool)
    }
  }

  await Promise.all(taskPool)

  // 通知后端合并分片
  await axios.post('http://127.0.0.1:3007/merge', {
    fileHash: hash,
    fileName: file.value.name,
    totalChunks,
  })
}
</script>

<template>
  <div class="-page">
    <h2>新文件上传</h2>
    <input type="file" @change="handleFileChange">
    <button :disabled="!file" @click="uploadFile">
      开始上传
    </button>
  </div>
</template>

<style lang="scss" scoped></style>
